package com.strong.sample.utils;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.Method;
import com.strong.utils.JSON;
import com.strong.utils.mvc.pojo.view.ReplyEnum;
import com.strong.utils.mvc.pojo.view.ReplyVO;
import com.strong.utils.mvc.pojo.view.vab.ReplyVabListVO;
import com.strong.utils.mvc.pojo.view.vab.VabList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import java.nio.charset.StandardCharsets;
import java.util.Map;

import static com.strong.sample.utils.MockMatcher.*;
import static com.strong.utils.security.AuthorityConstants.AUTHENTICATION_TEMPLATE;

@Slf4j
@Component
public class MockMvcUtils {

    public static final String MSG_CLASS_TYPE_NULL = "返回的class类型为空";
    public static final String MSG_FIELD_ARRAY_EMPTY = "包含字段数组为空";
    /**
     * 模拟mvc对象
     */
    @Autowired
    public MockMvc mockMvc;

    /**
     * 获取post提交返回的视图
     *
     * @param strUrl   URL地址
     * @param strToken 权限标记
     * @param objModel 提交的数据模型
     * @return {@link ResultActions }
     * @throws Exception 异常
     */
    public ResultActions postReturnResult(String strUrl, String strToken, Object objModel) throws Exception {
        // 发送请求
        return mockMvc.perform(postJsonRequestBuilder(strUrl, strToken, objModel)).andDo(logResult());
    }

    /**
     * 获取post提交返回的视图 断言包含strsIncludeField的字段
     *
     * @param strUrl           URL地址
     * @param strToken         权限标记
     * @param objModel         提交的数据模型
     * @param replyEnum        回复枚举
     * @param strsIncludeField 包含字段数组
     * @return {@link ResultActions }
     * @throws Exception 异常
     */
    public ResultActions postReturnResultReplyVO(String strUrl, String strToken, Object objModel, ReplyEnum replyEnum, String... strsIncludeField) throws Exception {
        return postReturnResult(strUrl, strToken, objModel).andExpect(isReplyModel(replyEnum.getCode(), strsIncludeField));
    }

    /**
     * 获取post提交返回的视图 断言包含strsIncludeField的字段 默认返回200代码
     *
     * @param strUrl           URL地址
     * @param strToken         权限标记
     * @param objModel         提交的数据模型
     * @param strsIncludeField 包含字段数组
     * @return {@link ResultActions }
     * @throws Exception 异常
     */
    public ResultActions postReturnResultReplyVO(String strUrl, String strToken, Object objModel, String... strsIncludeField) throws Exception {
        return postReturnResultReplyVO(strUrl, strToken, objModel, ReplyEnum.SUCCESS_RETURN_DATA, strsIncludeField);
    }

    /**
     * 获取post提交返回的视图 strsIncludeField非空则断言包含strsIncludeField字段，否则断言返回的V类型为ClassV
     *
     * @param strUrl               URL地址
     * @param strToken             权限标记
     * @param objModel             提交的数据模型
     * @param replyEnum            回复枚举
     * @param classV               返回的视图CLASS
     * @param strsIncludeFiledName 包含字段数组
     * @return {@link V }
     * @throws Exception 异常
     */
    public <V> V postReturnReplyVO(String strUrl, String strToken, Object objModel, ReplyEnum replyEnum, Class<V> classV, String... strsIncludeFiledName) throws Exception {
        // 如果【包含字段数组】为空，则默认取classV的 STRS_INCLUDE_PROPERTIES
        if (ArrayUtil.isEmpty(strsIncludeFiledName)) {
            Assert.notNull(classV, MSG_CLASS_TYPE_NULL);
            strsIncludeFiledName = getIncludeFiledNames(classV);
        }
        MvcResult mvcResult = postReturnResultReplyVO(strUrl, strToken, objModel, replyEnum, strsIncludeFiledName).andReturn();

        // 获取返回的ReplyVO对象
        ReplyVO<V> replyVO = JSON.parseReply(mvcResult.getResponse().getContentAsString(), classV);
        return replyVO.getData();
    }

    /**
     * 获取post提交返回的视图 断言返回的V类型为ClassV 默认返回200代码
     *
     * @param strUrl   URL地址
     * @param strToken 权限标记
     * @param objModel 提交的数据模型
     * @param classV   classv
     * @return {@link V }
     * @throws Exception 异常
     */
    public <V> V postReturnReplyVO(String strUrl, String strToken, Object objModel, Class<V> classV) throws Exception {
        return postReturnReplyVO(strUrl, strToken, objModel, ReplyEnum.SUCCESS_RETURN_DATA, classV);
    }

    /**
     * 获取post提交返回的视图 断言包含strsIncludeField的字段 默认返回200代码
     *
     * @param strUrl               URL地址
     * @param strToken             权限标记
     * @param objModel             提交的数据模型
     * @param strsIncludeFiledName 包含字段数组
     * @return {@link V }
     * @throws Exception 异常
     */
    public <V> V postReturnReplyVO(String strUrl, String strToken, Object objModel, String... strsIncludeFiledName) throws Exception {
        Assert.notEmpty(strsIncludeFiledName, MSG_FIELD_ARRAY_EMPTY);
        return postReturnReplyVO(strUrl, strToken, objModel, ReplyEnum.SUCCESS_RETURN_DATA, null, strsIncludeFiledName);
    }

    /**
     * 获取post提交返回的列表视图 断言包含strsIncludeField的字段
     *
     * @param strUrl               URL地址
     * @param strToken             权限标记
     * @param objModel             提交的数据模型
     * @param replyEnum            回复枚举
     * @param strsIncludeFiledName 包含字段数组
     * @return {@link ResultActions }
     * @throws Exception 异常
     */
    public ResultActions postResultListReplyVO(String strUrl, String strToken, Object objModel, ReplyEnum replyEnum, String... strsIncludeFiledName) throws Exception {
        return postReturnResult(strUrl, strToken, objModel).andExpect(isReplyList(replyEnum.getCode(), strsIncludeFiledName));
    }

    /**
     * 获取post提交返回的视图 strsIncludeField非空则断言包含strsIncludeField字段，否则断言返回的V类型为ClassV
     *
     * @param strUrl               URL地址
     * @param strToken             权限标记
     * @param objModel             提交的数据模型
     * @param replyEnum            回复枚举
     * @param classV               返回的视图CLASS
     * @param strsIncludeFiledName 包含字段数组
     * @return {@link V }
     * @throws Exception 异常
     */
    public <V> VabList<V> postReturnListReplyVO(String strUrl, String strToken, Object objModel, ReplyEnum replyEnum, Class<V> classV, String... strsIncludeFiledName) throws Exception {
        // 如果【包含字段数组】为空，则默认取classV的 STRS_INCLUDE_PROPERTIES
        if (ArrayUtil.isEmpty(strsIncludeFiledName)) {
            Assert.notNull(classV, MSG_CLASS_TYPE_NULL);
            strsIncludeFiledName = getIncludeFiledNames(classV);
        }
        MvcResult mvcResult = postResultListReplyVO(strUrl, strToken, objModel, replyEnum, strsIncludeFiledName).andReturn();

        ReplyVabListVO<V> replyVabListVO = JSON.parseReplyVabList(mvcResult.getResponse().getContentAsString(), classV);
        return replyVabListVO.getData();
    }

    /**
     * 获取post提交返回的视图 断言返回的V类型为ClassV 默认返回200代码
     *
     * @param strUrl   URL地址
     * @param strToken 权限标记
     * @param objModel 提交的数据模型
     * @param classV   返回的视图CLASS
     * @return {@link V }
     * @throws Exception 异常
     */
    public <V> VabList<V> postReturnListReplyVO(String strUrl, String strToken, Object objModel, Class<V> classV) throws Exception {
        return postReturnListReplyVO(strUrl, strToken, objModel, ReplyEnum.SUCCESS_RETURN_DATA, classV);
    }

    /**
     * 获取post提交返回的视图 断言包含strsIncludeField的字段 默认返回200代码
     *
     * @param strUrl               URL地址
     * @param strToken             权限标记
     * @param objModel             提交的数据模型
     * @param strsIncludeFiledName 包含字段数组
     * @return {@link V }
     * @throws Exception 异常
     */
    public <V> VabList<V> postReturnListReplyVO(String strUrl, String strToken, Object objModel, String... strsIncludeFiledName) throws Exception {
        return postReturnListReplyVO(strUrl, strToken, objModel, ReplyEnum.SUCCESS_RETURN_DATA, null, strsIncludeFiledName);
    }


    /**
     * post提交断言异常 言返回的字符串为strAssertMessage
     *
     * @param strUrl           URL地址
     * @param strToken         jwt 标记
     * @param objModel         提交的数据模型
     * @param strAssertMessage 断言消息
     * @throws Exception 异常
     */
    public void postAssertException(String strUrl, String strToken, Object objModel, String strAssertMessage) throws Exception {
        postReturnResultReplyVO(strUrl, strToken, objModel, ReplyEnum.ERROR_SERVER_ERROR)
                .andExpect(isExceptionReplyString(ReplyEnum.ERROR_SERVER_ERROR.getCode(), strAssertMessage));
    }

    /**
     * post提交断言异常
     * 首先遍历“映射断言字段消息”断言字段及断言消息
     * “映射断言字段消息”为空，则断言返回的字符串为“断言消息”
     *
     * @param strUrl                URL地址
     * @param strToken              jwt 标记
     * @param objModel              提交的数据模型
     * @param mapAssertFieldMessage 映射断言字段消息
     * @throws Exception 异常
     */
    public void postAssertException(String strUrl, String strToken, Object objModel, Map<String, String> mapAssertFieldMessage) throws Exception {
        postReturnResultReplyVO(strUrl, strToken, objModel, ReplyEnum.ERROR_SERVER_ERROR)
                .andExpect(isExceptionReplyJson(ReplyEnum.ERROR_SERVER_ERROR.getCode(), mapAssertFieldMessage));
    }

    /**
     * 记录结果
     */
    private ResultHandler logResult() {
        return result -> {
            log.info("\n\tURL地址：{}\n\t请求内容：{}\n\t返回代码：{}\n\t返回内容：{}",
                    result.getRequest().getRequestURL(),
                    result.getRequest().getContentAsString(),
                    result.getResponse().getStatus(),
                    result.getResponse().getContentAsString());
        };
    }

    /**
     * 获取MockMvc的MockHttpServletRequestBuilder
     *
     * @param method     方法
     * @param mediaType  媒体类型
     * @param strUrl     Url
     * @param strToken   jwt token
     * @param strContent 内容
     * @return {@link MockHttpServletRequestBuilder }
     */
    private MockHttpServletRequestBuilder requestBuilder(Method method, MediaType mediaType, String strUrl, String strToken, String strContent) {
        Assert.notBlank(strUrl, "URL地址为空");
        Assert.notNull(method, "HTTP请求的方法为空");
        Assert.notNull(mediaType, "HTTP请求媒体类型为空");

        if (!StrUtil.startWith(strUrl, "/")) {
            strUrl = "/" + strUrl;
        }

        // 根据Method发送不同请求
        MockHttpServletRequestBuilder accept;
        if (method == Method.POST) {
            accept = MockMvcRequestBuilders.post(strUrl);
        } else {
            accept = MockMvcRequestBuilders.get(strUrl);
        }

        // token存在 则加入jwt请求header
        if (StrUtil.isNotBlank(strToken)) {
            accept = accept.header(HttpHeaders.AUTHORIZATION, String.format(AUTHENTICATION_TEMPLATE, strToken));
        }

        // 存在提交内容 则加入
        if (StrUtil.isNotBlank(strContent)) {
            accept = accept.content(strContent);
        }

        // 加入请求和返回类型
        return accept.contentType(MediaType.APPLICATION_JSON_VALUE)
                .accept(new MediaType(mediaType, StandardCharsets.UTF_8));
    }

    /**
     * 使用get发送json内容请求，并返回json格式内容
     *
     * @param strUrl     url
     * @param strToken   jwt token
     * @param strContent 提交的内容
     * @return {@link MockHttpServletRequestBuilder }
     */
    private MockHttpServletRequestBuilder getJsonRequestBuilder(String strUrl, String strToken, String strContent) {
        return requestBuilder(Method.GET, MediaType.APPLICATION_JSON, strUrl, strToken, strContent);
    }

    /**
     * 使用get发送带token的对象（转json）内容请求，并返回json格式内容
     *
     * @param strUrl   url
     * @param strToken 标记
     * @param objModel 模型
     * @return {@link MockHttpServletRequestBuilder }
     */
    private MockHttpServletRequestBuilder getJsonRequestBuilder(String strUrl, String strToken, Object objModel) {
        return getJsonRequestBuilder(strUrl, strToken, JSON.toJSONString(objModel));
    }

    /**
     * 使用post发送json内容请求，并返回json格式内容
     *
     * @param strUrl     url
     * @param strToken   标记
     * @param strContent 提交的内容
     * @return {@link MockHttpServletRequestBuilder }
     */
    private MockHttpServletRequestBuilder postJsonRequestBuilder(String strUrl, String strToken, String strContent) {
        return requestBuilder(Method.POST, MediaType.APPLICATION_JSON, strUrl, strToken, strContent);
    }

    /**
     * 使用post发送带token的对象（转json）内容请求，并返回json格式内容
     *
     * @param strUrl   url
     * @param strToken 标记
     * @param objModel 模型
     * @return {@link MockHttpServletRequestBuilder }
     */
    private MockHttpServletRequestBuilder postJsonRequestBuilder(String strUrl, String strToken, Object objModel) {
        if (ObjUtil.isNotNull(objModel)) {
            return postJsonRequestBuilder(strUrl, strToken, JSON.toJSONString(objModel));
        } else {
            return postJsonRequestBuilder(strUrl, strToken, null);
        }
    }
}
